﻿using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Formatters;
using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

using BizElements.Core;

namespace BizElements.BusinessLayer
{
    /// <summary>A <see cref="System.Diagnostics.TraceListener"/> that writes the trace data into AppLog table using ElementsFx OR/M.
    /// Formats the output with an <see cref="ILogFormatter"/>.</summary>
    [ConfigurationElementType(typeof(AppLogTableTraceListenerData))]
    public class AppLogTableTraceListener : FormattedTraceListenerBase
    {
        const string PairDelimiter = ";";
        const string KeyValueDelimiter = "=";

        /// <summary>Initializes a new instance of <see cref="AppLogTableTraceListener"/>.</summary>
        /// <param name="formatter">The formatter.</param>    
        public AppLogTableTraceListener(ILogFormatter formatter)
            : base(formatter)
        {
        }

        /// <summary>Delivers the trace data to the <b>BizElements.Services/WsLogReceiver.asmx</b> Web service.</summary>
        /// <param name="eventCache">The context information provided by <see cref="System.Diagnostics"/>.</param>
        /// <param name="source">The name of the trace source that delivered the trace data.</param>
        /// <param name="eventType">The type of event.</param>
        /// <param name="id">The id of the event.</param>
        /// <param name="data">The <see cref="LogEntry"/> object to trace. Ignored if not an instance of LogEntry class.</param>
        public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
        {
            // Only LogEntry data is supported.
            LogEntry logEntry = data as LogEntry;
            if (logEntry != null)
            {
                AppLogEntity appLogRecord = BuildEntityFromLogEntry(logEntry);
                bool hasErrors = ValidateAppLogRecord(appLogRecord);
                if (!hasErrors)
                {
                    IEntityDAO dao = new EntityDAO(appLogRecord, new AppLogCatalog().CreateConnectionProvider());
                    dao.InsertOne();
                }
            }
        }

        private AppLogEntity BuildEntityFromLogEntry(LogEntry logEntry)
        {
            string formattedMessage = GenerateFormattedMessage(logEntry);
            IDictionary<string, object> details = logEntry.ExtendedProperties ?? new Dictionary<string, object>();
            return new AppLogEntity() 
            { 
                Title = logEntry.Title,
                Timestamp = logEntry.TimeStamp,
                MachineName = logEntry.MachineName,
                AppDomainName = logEntry.AppDomainName,
                Message = logEntry.Message,
                FormattedMessage = formattedMessage,
                ActorID = GetValue(details, "ActorID"),
                ActorName = GetValue(details, "ActorName"),
                ObjectType = GetValue(details, "ObjectType"),
                ObjectID = GetValue(details, "ObjectID"),
                Category = GetValue(details, "Category"),
                Status = 1                 
            };
        }

        /// <summary>Generates content that will be saved to FormattedMessage field.</summary>
        /// <param name="logEntry">Log entry.</param>
        /// <returns>Formatted message.</returns>
        protected virtual string GenerateFormattedMessage(LogEntry logEntry)
        {
            IDictionary<string, object> details = logEntry.ExtendedProperties ?? new Dictionary<string, object>();
            StringBuilder formattedMessage = new StringBuilder();
            foreach (KeyValuePair<string, object> propertyAndValue in details)
            {
                formattedMessage.Append(propertyAndValue.Key);
                formattedMessage.Append(KeyValueDelimiter);
                formattedMessage.Append(propertyAndValue.Value);
                formattedMessage.Append(PairDelimiter);
            }

            return formattedMessage.ToString();
        }

        private static Dictionary<string, string> TryParseDictionary(string concatenatedKeysAndValues, char pairDelimiter, char keyValueDelimiter)
        {
            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            string[] keyValuePairs = concatenatedKeysAndValues.Split(new char[] { pairDelimiter }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string keyValue in keyValuePairs)
            {
                int equalIdx = keyValue.IndexOf(keyValueDelimiter);
                if (equalIdx > 0)
                {
                    string key = keyValue.Substring(0, equalIdx).Trim();
                    string value = keyValue.Substring(equalIdx + 1).Trim();
                    dictionary[key] = value;
                }
            }

            return dictionary;
        }

        private static string GetValue(IDictionary<string, object> dictionary, string key)
        {
            object value;
            dictionary.TryGetValue(key, out value);
            return Convert.ToString(value);
        }

        private static bool ValidateAppLogRecord(AppLogEntity appLogRecord)
        {
            FieldError[] constraints = appLogRecord.ValidateAllFields();
            bool hasErrors = ArrayUtil.Exists<FieldError>(constraints, (errCode) => errCode.ErrorCode != FieldErrorCode.AllOk);
            return hasErrors;
        }

        /// <summary>Does nothing.</summary>
        /// <param name="message">The message to log. Ignored.</param>
        public override void Write(string message)
        {
        }

        /// <summary>Does nothing.</summary>
        /// <param name="message">The message to log. Ignored.</param>
        public override void WriteLine(string message)
        {
            Write(message);
        }
    }
}
